Go serveur OAuth2

Ce service implémente la spécification OAuth 2.0 . Des extraits de la spécification sont inclus dans ce fichier README pour décrire les différents types de subventions. Veuillez lire la spécification complète pour plus d'informations.

OAuth 2.0

Authentification du client

http://tools.ietf.org/html/rfc6749#section-3.2.1

Les clients doivent s'authentifier avec les informations d'identification du client (ID client et secret) lors de l'émission de demandes au  point de terminaison /v1/oauth/tokens. L'authentification HTTP de base doit être utilisée.

Types de subventions

Code d'autorisation

http://tools.ietf.org/html/rfc6749#section-4.1

Le type d'octroi de code d'autorisation est utilisé pour obtenir à la fois des jetons d'accès et des jetons d'actualisation et est optimisé pour les clients confidentiels. Puisqu'il s'agit d'un flux basé sur la redirection, le client doit être capable d'interagir avec l'agent utilisateur du propriétaire de la ressource (généralement un navigateur Web) et capable de recevoir des demandes entrantes (via la redirection) du serveur d'autorisation.

+----------+

| Resource |

|   Owner  |

|          |

+----------+

     ^

     |

    (B)

+----|-----+          Client Identifier      +---------------+

|         -+----(A)-- & Redirection URI ---->|               |

|  User-   |                                 | Authorization |

|  Agent  -+----(B)-- User authenticates --->|     Server    |

|          |                                 |               |

|         -+----(C)-- Authorization Code ---<|               |

+-|----|---+                                 +---------------+

  |    |                                         ^      v

 (A)  (C)                                        |      |

  |    |                                         |      |

  ^    v                                         |      |

+---------+                                      |      |

|         |>---(D)-- Authorization Code ---------'      |

|  Client |          & Redirection URI                  |

|         |                                             |

|         |<---(E)----- Access Token -------------------'

+---------+       (w/ Optional Refresh Token)

Le client lance le flux en dirigeant l'agent utilisateur du propriétaire de la ressource vers le point de terminaison d'autorisation. Le client inclut son identifiant client, la portée demandée, l'état local et un URI de redirection vers lequel le serveur d'autorisation renverra l'agent utilisateur une fois que l'accès est accordé (ou refusé).

http://localhost:8080/web/authorize?client_id=test_client_1&redirect_uri=https%3A%2F%2Fwww.example.com&response_type=code&state=somestate&scope=read_write

Le serveur d'autorisation authentifie le propriétaire de la ressource (via l'agent utilisateur).

Capture d'écran de la page de connexionCapture d'écran de la page de connexion

Le serveur d'autorisation établit ensuite si le propriétaire de la ressource accorde ou refuse la demande d'accès du client.

Capture d'écran de la page d'autorisationCapture d'écran de la page d'autorisation

Si la demande échoue en raison d'un URI de redirection manquant, invalide ou incompatible, ou si l'identifiant client est manquant ou invalide, le serveur d'autorisation DEVRAIT informer le propriétaire de la ressource de l'erreur et NE DOIT PAS rediriger automatiquement l'agent utilisateur vers la redirection invalide URI.

Si le propriétaire de la ressource refuse la demande d'accès ou si la demande échoue pour des raisons autres qu'un URI de redirection manquant ou non valide, le serveur d'autorisation informe le client en ajoutant le paramètre d'erreur au composant de requête de l'URI de redirection.

https://www.example.com/?error=access_denied&state=somestate

En supposant que le propriétaire de la ressource accorde l'accès, le serveur d'autorisation redirige l'agent utilisateur vers le client en utilisant l'URI de redirection fourni précédemment (dans la demande ou lors de l'enregistrement du client). L'URI de redirection comprend un code d'autorisation et tout état local fourni précédemment par le client.

https://www.example.com/?code=7afb1c55-76e4-4c76-adb7-9d657cb47a27&state=somestate

Le client demande un jeton d'accès au point de terminaison de jeton du serveur d'autorisation en incluant le code d'autorisation reçu à l'étape précédente. Lors de la demande, le client s'authentifie auprès du serveur d'autorisation. Le client inclut l'URI de redirection utilisé pour obtenir le code d'autorisation pour la vérification.

curl --compressed -v localhost:8080/v1/oauth/tokens \

        -u test_client_1:test_secret \

        -d "grant_type=authorization_code" \

        -d "code=7afb1c55-76e4-4c76-adb7-9d657cb47a27" \

        -d "redirect_uri=https://www.example.com"

 

Le serveur d'autorisation authentifie le client, valide le code d'autorisation et s'assure que l'URI de redirection reçu correspond à l'URI utilisé pour rediriger le client auparavant. S'il est valide, le serveur d'autorisation répond avec un jeton d'accès et, éventuellement, un jeton d'actualisation.

{

  "user_id": "1",

  "access_token": "00ccd40e-72ca-4e79-a4b6-67c95e2e3f1c",

  "expires_in": 3600,

  "token_type": "Bearer",

  "scope": "read_write",

  "refresh_token": "6fd8d272-375a-4d8a-8d0f-43367dc8b791"

}

Implicite

http://tools.ietf.org/html/rfc6749#section-4.2

Le type d'octroi implicite est utilisé pour obtenir des jetons d'accès (il ne prend pas en charge l'émission de jetons d'actualisation) et est optimisé pour les clients publics connus pour exploiter un URI de redirection particulier. Ces clients sont généralement implémentés dans un navigateur à l'aide d'un langage de script tel que JavaScript.

Puisqu'il s'agit d'un flux basé sur la redirection, le client doit être capable d'interagir avec l'agent utilisateur du propriétaire de la ressource (généralement un navigateur Web) et capable de recevoir des demandes entrantes (via la redirection) du serveur d'autorisation.

Contrairement au type d'octroi de code d'autorisation, dans lequel le client effectue des demandes distinctes d'autorisation et de jeton d'accès, le client reçoit le jeton d'accès à la suite de la demande d'autorisation.

Le type d'octroi implicite n'inclut pas l'authentification du client et repose sur la présence du propriétaire de la ressource et l'enregistrement de l'URI de redirection. Étant donné que le jeton d'accès est codé dans l'URI de redirection, il peut être exposé au propriétaire de la ressource et à d'autres applications résidant sur le même appareil.

+----------+

| Resource |

|  Owner   |

|          |

+----------+

     ^

     |

    (B)

+----|-----+          Client Identifier     +---------------+

|         -+----(A)-- & Redirection URI --->|               |

|  User-   |                                | Authorization |

|  Agent  -|----(B)-- User authenticates -->|     Server    |

|          |                                |               |

|          |<---(C)--- Redirection URI ----<|               |

|          |          with Access Token     +---------------+

|          |            in Fragment

|          |                                +---------------+

|          |----(D)--- Redirection URI ---->|   Web-Hosted  |

|          |          without Fragment      |     Client    |

|          |                                |    Resource   |

|     (F)  |<---(E)------- Script ---------<|               |

|          |                                +---------------+

+-|--------+

  |    |

 (A)  (G) Access Token

  |    |

  ^    v

+---------+

|         |

|  Client |

|         |

+---------+

Le client lance le flux en dirigeant l'agent utilisateur du propriétaire de la ressource vers le point de terminaison d'autorisation. Le client inclut son identifiant client, la portée demandée, l'état local et un URI de redirection vers lequel le serveur d'autorisation renverra l'agent utilisateur une fois que l'accès est accordé (ou refusé).

http://localhost:8080/web/authorize?client_id=test_client_1&redirect_uri=https%3A%2F%2Fwww.example.com&response_type=token&state=somestate&scope=read_write

Le serveur d'autorisation authentifie le propriétaire de la ressource (via l'agent utilisateur).

Capture d'écran de la page de connexionCapture d'écran de la page de connexion

Le serveur d'autorisation établit ensuite si le propriétaire de la ressource accorde ou refuse la demande d'accès du client.

Capture d'écran de la page d'autorisationCapture d'écran de la page d'autorisation

Si la demande échoue en raison d'un URI de redirection manquant, invalide ou incompatible, ou si l'identifiant client est manquant ou invalide, le serveur d'autorisation DEVRAIT informer le propriétaire de la ressource de l'erreur et NE DOIT PAS rediriger automatiquement l'agent utilisateur vers la redirection invalide URI.

Si le propriétaire de la ressource refuse la demande d'accès ou si la demande échoue pour des raisons autres qu'un URI de redirection manquant ou non valide, le serveur d'autorisation informe le client en ajoutant les paramètres suivants au composant fragment de l'URI de redirection.

https://www.example.com/#error=access_denied&state=somestate

En supposant que le propriétaire de la ressource accorde l'accès, le serveur d'autorisation redirige l'agent utilisateur vers le client à l'aide de l'URI de redirection fourni précédemment. L'URI de redirection inclut le jeton d'accès dans le fragment d'URI.

https://www.example.com/#access_token=087902d5-29e7-417b-a339-b57a60d6742a&expires_in=3600&scope=read_write&state=somestate&token_type=Bearer

L'agent utilisateur suit les instructions de redirection en faisant une demande à la ressource client hébergée sur le Web (qui n'inclut pas le fragment par [RFC2616]). L'agent utilisateur conserve les informations de fragment localement.

La ressource client hébergée sur le Web renvoie une page Web (généralement un document HTML avec un script intégré) capable d'accéder à l'URI de redirection complète, y compris le fragment conservé par l'agent utilisateur, et d'extraire le jeton d'accès (et d'autres paramètres) contenu dans le fragment.

L'agent utilisateur exécute localement le script fourni par la ressource client hébergée sur le Web, qui extrait le jeton d'accès.

L'agent utilisateur transmet le jeton d'accès au client.

Informations d'identification du mot de passe du propriétaire de la ressource

http://tools.ietf.org/html/rfc6749#section-4.3

Le type d'octroi d'informations d'identification de mot de passe du propriétaire de la ressource convient dans les cas où le propriétaire de la ressource a une relation d'approbation avec le client, comme le système d'exploitation du périphérique ou une application hautement privilégiée. Le serveur d'autorisation doit faire particulièrement attention lors de l'activation de ce type d'octroi et ne l'autoriser que lorsque d'autres flux ne sont pas viables.

Ce type d'accord convient aux clients capables d'obtenir les informations d'identification du propriétaire de la ressource (nom d'utilisateur et mot de passe, généralement à l'aide d'un formulaire interactif). Il est également utilisé pour migrer les clients existants à l'aide de schémas d'authentification directe tels que l'authentification HTTP Basic ou Digest vers OAuth en convertissant les informations d'identification stockées en jeton d'accès.

+----------+

| Resource |

|  Owner   |

|          |

+----------+

     v

     |    Resource Owner

     (A) Password Credentials

     |

     v

+---------+                                  +---------------+

|         |>--(B)---- Resource Owner ------->|               |

|         |         Password Credentials     | Authorization |

| Client  |                                  |     Server    |

|         |<--(C)---- Access Token ---------<|               |

|         |    (w/ Optional Refresh Token)   |               |

+---------+                                  +---------------+

 

Le propriétaire de la ressource fournit au client son nom d'utilisateur et son mot de passe.

Le client demande un jeton d'accès au point de terminaison de jeton du serveur d'autorisation en incluant les informations d'identification reçues du propriétaire de la ressource. Lors de la demande, le client s'authentifie auprès du serveur d'autorisation.

curl --compressed -v localhost:8080/v1/oauth/tokens \

        -u test_client_1:test_secret \

        -d "grant_type=password" \

        -d "username=test@user" \

        -d "password=test_password" \

        -d "scope=read_write"

Le serveur d'autorisation authentifie le client et valide les informations d'identification du propriétaire de la ressource et, si elles sont valides, émet un jeton d'accès.

{

  "user_id": "1",

  "access_token": "00ccd40e-72ca-4e79-a4b6-67c95e2e3f1c",

  "expires_in": 3600,

  "token_type": "Bearer",

  "scope": "read_write",

  "refresh_token": "6fd8d272-375a-4d8a-8d0f-43367dc8b791"

}

Identifiants du client

http://tools.ietf.org/html/rfc6749#section-4.4

Le client peut demander un jeton d'accès en utilisant uniquement ses informations d'identification client (ou d'autres moyens d'authentification pris en charge) lorsque le client demande l'accès aux ressources protégées sous son contrôle, ou à celles d'un autre propriétaire de ressources qui ont été préalablement arrangées avec le serveur d'autorisation ( dont la méthode sort du cadre de cette spécification).

Le type d'octroi d'informations d'identification de client DOIT être utilisé uniquement par des clients confidentiels.

+---------+                                  +---------------+

|         |                                  |               |

|         |>--(A)- Client Authentication --->| Authorization |

| Client  |                                  |     Server    |

|         |<--(B)---- Access Token ---------<|               |

|         |                                  |               |

+---------+                                  +---------------+

Le client s'authentifie auprès du serveur d'autorisation et demande un jeton d'accès au point de terminaison du jeton.

curl --compressed -v localhost:8080/v1/oauth/tokens \

        -u test_client_1:test_secret \

        -d "grant_type=client_credentials" \

        -d "scope=read_write"

Le serveur d'autorisation authentifie le client et, s'il est valide, émet un jeton d'accès.

{

  "access_token": "00ccd40e-72ca-4e79-a4b6-67c95e2e3f1c",

  "expires_in": 3600,

  "token_type": "Bearer",

  "scope": "read_write",

  "refresh_token": "6fd8d272-375a-4d8a-8d0f-43367dc8b791"

}

Actualisation d'un jeton d'accès

http://tools.ietf.org/html/rfc6749#section-6

Si le serveur d'autorisation a émis un jeton d'actualisation au client, le client peut faire une demande d'actualisation au point de terminaison de jeton afin d'actualiser le jeton d'accès.

curl --compressed -v localhost:8080/v1/oauth/tokens \

        -u test_client_1:test_secret \

        -d "grant_type=refresh_token" \

        -d "refresh_token=6fd8d272-375a-4d8a-8d0f-43367dc8b791"

Le serveur d'autorisation DOIT:

S'il est valide et autorisé, le serveur d'autorisation émet un jeton d'accès.

{

  "user_id": "1",

  "access_token": "1f962bd5-7890-435d-b619-584b6aa32e6c",

  "expires_in": 3600,

  "token_type": "Bearer",

  "scope": "read_write",

  "refresh_token": "3a6b45b8-9d29-4cba-8a1b-0093e8a2b933"

}

Le serveur d'autorisation PEUT émettre un nouveau jeton de rafraîchissement, auquel cas le client DOIT rejeter l'ancien jeton de rafraîchissement et le remplacer par le nouveau jeton de rafraîchissement. Le serveur d'autorisation PEUT révoquer l'ancien jeton de rafraîchissement après avoir émis un nouveau jeton de rafraîchissement au client. Si un nouveau jeton de rafraîchissement est émis, la portée du jeton de rafraîchissement DOIT être identique à celle du jeton de rafraîchissement inclus par le client dans la demande.

Introspection de jetons

https://tools.ietf.org/html/rfc7662

Si le serveur d'autorisation a émis un jeton d'accès ou un jeton d'actualisation au client, le client peut faire une demande au point de terminaison introspect afin d'apprendre des méta-informations sur un jeton.

curl --compressed -v localhost:8080/v1/oauth/introspect \

        -u test_client_1:test_secret \

        -d "token=00ccd40e-72ca-4e79-a4b6-67c95e2e3f1c" \

        -d "token_type_hint=access_token"

Le serveur d'autorisation répond aux méta-informations sur un jeton.

{

  "active": true,

  "scope": "read_write",

  "client_id": "test_client_1",

  "username": "test@username",

  "token_type": "Bearer",

  "exp": 1454868090

}

Plugins

Ce serveur est facilement étendu ou modifié grâce à l'utilisation de plugins. Quatre services, santé , oauth , session et web sont disponibles pour modification.

Afin d'implémenter un plugin:

  1. 1.Créez votre propre interface qui implémente toutes les méthodes du service que vous remplacez.  

  2. 2.Modifiez cmd/run_server.gopour utiliser votre service en appelant le session.Use[service-you-are-replaceing]Service(yourCustomService.NewService())avant que les services ne soient initialisés via services.Init(cnf, db).  

Par exemple, pour implémenter un plugin de stockage de session redis disponible :

// $ go get https://github.com/adam-hanna/redis-sessions

//

// cmd/run_server.go

import (

    ...

    "github.com/adam-hanna/redis-sessions/redis"

    ...

)

 

// RunServer runs the app

func RunServer(configBackend string) error {

    ...

 

    // configure redis for session store

    sessionSecrets := make([][]byte, 1)

    sessionSecrets[0] = []byte(cnf.Session.Secret)

    redisConfig := redis.ConfigType{

        Size:           10,

        Network:        "tcp",

        Address:        ":6379",

        Password:       "",

        SessionSecrets: sessionSecrets,

    }

 

    // start the services

    services.UseSessionService(redis.NewService(cnf, redisConfig))

    if err := services.InitServices(cnf, db); err != nil {

        return err

    }

    defer services.CloseServices()

 

    ...

}

Stockage de session

Par défaut, ce serveur implémente des sessions de cookies en mémoire via des sessions Gorilla .

Cependant, comme le service de session peut être remplacé via un plugin, toutes les implémentations de stockage de sessions Gorilla disponibles peuvent être enveloppées par session.ServiceInterface.

Dépendances

Depuis Go 1.11, un nouveau système de gestion des dépendances recommandé est via des modules .

C'est l'une des légères faiblesses de Go car la gestion des dépendances n'est pas un problème résolu. Auparavant, Go recommandait officiellement d'utiliser l' outil dep, mais cela a été abandonné au profit des modules.

Installer

Pour le stockage de configuration distribué, vous pouvez utiliser etcd ou consul (etcd étant la valeur par défaut)

Si vous développez sous OSX, installez etcdou consul, Postgreset nats-streaming-server:

etcd

infusion installer etcd

Chargez une configuration de développement dans etcd:

ETCDCTL_API=3 etcdctl put /config/go_oauth2_server.json '{

  "Database": {

    "Type": "postgres",

    "Host": "localhost",

    "Port": 5432,

    "User": "go_oauth2_server",

    "Password": "",

    "DatabaseName": "go_oauth2_server",

    "MaxIdleConns": 5,

    "MaxOpenConns": 5

  },

  "Oauth": {

    "AccessTokenLifetime": 3600,

    "RefreshTokenLifetime": 1209600,

    "AuthCodeLifetime": 3600

  },

  "Session": {

    "Secret": "test_secret",

    "Path": "/",

    "MaxAge": 604800,

    "HTTPOnly": true

  },

  "IsDevelopment": true

}'

Si vous utilisez l'API etcd version 3, utilisez à la etcdctl putplace de etcdctl set.

Vérifiez que la configuration a été chargée correctement:

ETCDCTL_API = 3 etcdctl get /config/go_oauth2_server.json

consul

brew install consul

Chargez une configuration de développement dans consul:

consul kv put /config/go_oauth2_server.json '{

  "Database": {

    "Type": "postgres",

    "Host": "localhost",

    "Port": 5432,

    "User": "go_oauth2_server",

    "Password": "",

    "DatabaseName": "go_oauth2_server",

    "MaxIdleConns": 5,

    "MaxOpenConns": 5

  },

  "Oauth": {

    "AccessTokenLifetime": 3600,

    "RefreshTokenLifetime": 1209600,

    "AuthCodeLifetime": 3600

  },

  "Session": {

    "Secret": "test_secret",

    "Path": "/",

    "MaxAge": 604800,

    "HTTPOnly": true

  },

  "IsDevelopment": true

}'

Vérifiez que la configuration a été chargée correctement:

consul kv get /config/go_oauth2_server.json

Postgres

brew install postgres

Vous souhaiterez peut-être créer une Postgresbase de données:

createuser --createdb go_oauth2_server

createdb -U go_oauth2_server go_oauth2_server

Compiler et exécuter

Compilez l'application:

go install .

Le binaire accepte un indicateur facultatif --configBackendqui peut être défini sur etcd | consul, par défaut suretcd

Exécutez les migrations:

go-oauth2-server migrate

Et enfin, exécutez l'application:

go-oauth2-server runserver

Lors du déploiement, vous pouvez définir des variables d'environnement liées à etcd:

Vous pouvez également définir des variables liées au consul

et les commandes équivalentes ci-dessus seraient

go-oauth2-server --configBackend consul migrate

go-oauth2-server --configBackend consul runserver

Essai

J'ai utilisé un mélange de tests unitaires et fonctionnels, vous devez donc les avoir sqliteinstallés pour que les tests s'exécutent avec succès, car la suite crée une base de données en mémoire.

Pour exécuter des tests:

make test